<?php
/**
 * crcl/base
 *
 * Copyright 2013 CRCL - André Sabosch - info@crcl.de
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * String
 *
 * Some chainable methods to work with strings
 * \crcl\base\String::getInstance($sInput)->doStartWith(...
 */

namespace crcl\base;

interface IString
{
  /**
   * returns current string
   *
   * @return string
   */
  public function toString();
  public function __toString();

  /**
   * Like str_replace, but replaces just the first occurrence
   *
   * @chainable
   *
   * @param string  $sSearch  replace this
   * @param string  $sReplace with this
   * @param string  $sReplace in this
   *
   * @return $this
   */
  public function replaceFirst($sSearch, $sReplace);

  /**
   * Adds $sString front of current string if current string does not start with $sString
   * If current string is empty, current string is set to $sString
   *
   * @chainable
   * @param string  $sString  String to add
   *
   * @return  $this
   */
  public function doStartWith($sString);

  /**
   * Cut first character if it matches $sString
   *
   * @chainable
   * @param string  $sString  String to cut
   *
   * @return $this
   */
  public function doNotStartWith($sString);

  /**
   * Adds $sString to the end of current string, if current string does not end with $sString
   * If current string is empty, current string is set to $sString
   *
   * @chainable
   * @param string $sString String to add
   *
   * @return  $this
   */
  public function doEndWith($sString);

  /**
   * Cut last character if it matches $sString
   *
   * @chainable
   * @param string  $sString  String to cut
   *
   * @return  $this
   */
  public function doNotEndWith($sString);

  /**
   * Removes BOM bytes from current string
   * 
   * @chainable
   * @return $this
   */
  public function removeBOM();
}

class String extends \crcl\base\instance\Factory implements IString
{
  public static function factory($sString)
  {
    if (!\is_string($sString))
    {
      throw new string\Exception(
        'No string',
        string\Exception::ERROR_NOSTRING,
        array(
             'invalid' => $sString,
             'type' => \gettype($sString),
             'export' => \var_export($sString, \TRUE)));
    }
    $oInstance = new String($sString);
    return $oInstance;
  }

  public function __construct($sString)
  {
    $this->sString = $sString;
  }

  public function toString()
  {
    $this->sString = (string)$this->sString;
    return $this->sString;
  }

  public function __toString()
  {
    return $this->toString();
  }

  public function replaceFirst($sSearch, $sReplace)
  {
    if ((\strlen($this->sString) > 0) && (\strlen($sSearch) > 0))
    {
      $iPosStart = \strpos($this->sString, $sSearch);
      if ($iPosStart !== \FALSE)
      {
        $iLength = \strlen($this->sString);
        $iPosEnd = $iPosStart + \strlen($sSearch);
        $this->sString = \substr($this->sString, 0, $iPosStart) . $sReplace .
          \substr($this->sString, $iPosEnd, ($iLength - $iPosEnd));
      }
    }
    return $this;
  }

  public function doStartWith($sString)
  {
    if ($this->sString == '')
    {
      $this->sString = $sString;
    }
    $sTest = \substr($this->sString, 0, \strlen($sString));
    if ($sTest != $sString)
    {
      $this->sString = $sString . $this->sString;
    }
    return $this;
  }

  public function doNotStartWith($sString)
  {
    if ($this->sString != '')
    {
      $iLength = \strlen($sString);
      $sTest = \substr($this->sString, 0, $iLength);
      if ($sTest === $sString)
      {
        $this->sString = \substr($this->sString, $iLength);
      }
    }
    return $this;
  }

  public function doEndWith($sString)
  {
    if ($this->sString != '')
    {
      $sTest = \substr($this->sString, -\strlen($sString));
      if ($sTest != $sString)
      {
        $this->sString = $this->sString . $sString;
      }
    }
    else
    {
      $this->sString = $sString;
    }
    return $this;
  }

  public function doNotEndWith($sString)
  {
    if ($this->sString != '')
    {
      $sTest = \substr($this->sString, -\strlen($sString));
      if ($sTest == $sString)
      {
        $iPos = \strrpos($this->sString, $sString); //must exist because of last test
        $this->sString = \substr($this->sString, 0, $iPos);
      }
    }
    return $this;
  }
  
  public function removeBOM()
  {
    if (\strlen($this->sString) > 3)
    {
      $this->doNotStartWith(self::BOM);
    }
    return $this;    
  }
  
  /** PRIVATE *************************************************************************************/

  const BOM = "\xef\xbb\xbf";
  
  private $sString;
}